1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.google.common.collect;
18
19 import com.google.common.base.Function;
20 import com.google.gwt.user.client.Timer;
21
22 import java.util.LinkedHashMap;
23 import java.util.Map;
24 import java.util.concurrent.ConcurrentHashMap;
25 import java.util.concurrent.ConcurrentMap;
26 import java.util.concurrent.TimeUnit;
27
28
29
30
31
32
33
34 public final class MapMaker extends GenericMapMaker<Object, Object> {
35
36
37
38
39 private static class ExpiringComputingMap<K, V> extends LinkedHashMap<K, V>
40 implements ConcurrentMap<K, V> {
41 private final long expirationMillis;
42 private final Function<? super K, ? extends V> computer;
43 private final int maximumSize;
44
45 ExpiringComputingMap(
46 long expirationMillis, int maximumSize, int initialCapacity) {
47 this(expirationMillis, null, maximumSize, initialCapacity);
48 }
49
50 ExpiringComputingMap(long expirationMillis, Function<? super K, ? extends V> computer,
51 int maximumSize, int initialCapacity) {
52 super(initialCapacity, 0.75f, (maximumSize != -1));
53 this.expirationMillis = expirationMillis;
54 this.computer = computer;
55 this.maximumSize = maximumSize;
56 }
57
58 @Override
59 public V put(K key, V value) {
60 V result = super.put(key, value);
61 if (expirationMillis > 0) {
62 scheduleRemoval(key, value);
63 }
64 return result;
65 }
66
67 @Override
68 protected boolean removeEldestEntry(Map.Entry<K, V> ignored) {
69 return (maximumSize == -1) ? false : size() > maximumSize;
70 }
71
72 @Override
73 public V putIfAbsent(K key, V value) {
74 if (!containsKey(key)) {
75 return put(key, value);
76 } else {
77 return get(key);
78 }
79 }
80
81 @Override
82 public boolean remove(Object key, Object value) {
83 if (containsKey(key) && get(key).equals(value)) {
84 remove(key);
85 return true;
86 }
87 return false;
88 }
89
90 @Override
91 public boolean replace(K key, V oldValue, V newValue) {
92 if (containsKey(key) && get(key).equals(oldValue)) {
93 put(key, newValue);
94 return true;
95 }
96 return false;
97 }
98
99 @Override
100 public V replace(K key, V value) {
101 return containsKey(key) ? put(key, value) : null;
102 }
103
104 private void scheduleRemoval(final K key, final V value) {
105
106
107
108
109
110
111
112
113 Timer timer = new Timer() {
114 @Override
115 public void run() {
116 remove(key, value);
117 }
118 };
119 timer.schedule((int) expirationMillis);
120 }
121
122 @Override
123 public V get(Object k) {
124
125 V result = super.get(k);
126 if (result == null && computer != null) {
127
128
129
130
131
132
133
134
135
136 @SuppressWarnings("unchecked")
137 K key = (K) k;
138 result = compute(key);
139 }
140 return result;
141 }
142
143 private V compute(K key) {
144
145 V value;
146 try {
147 value = computer.apply(key);
148 } catch (Throwable t) {
149 throw new ComputationException(t);
150 }
151
152 if (value == null) {
153 String message = computer + " returned null for key " + key + ".";
154 throw new NullPointerException(message);
155 }
156 put(key, value);
157 return value;
158 }
159 }
160
161 private int initialCapacity = 16;
162 private long expirationMillis = 0;
163 private int maximumSize = -1;
164 private boolean useCustomMap;
165
166 public MapMaker() {}
167
168 @Override
169 public MapMaker initialCapacity(int initialCapacity) {
170 if (initialCapacity < 0) {
171 throw new IllegalArgumentException();
172 }
173 this.initialCapacity = initialCapacity;
174 return this;
175 }
176
177 @Override
178 MapMaker expireAfterWrite(long duration, TimeUnit unit) {
179 if (expirationMillis != 0) {
180 throw new IllegalStateException(
181 "expiration time of " + expirationMillis + " ns was already set");
182 }
183 if (duration <= 0) {
184 throw new IllegalArgumentException("invalid duration: " + duration);
185 }
186 this.expirationMillis = unit.toMillis(duration);
187 useCustomMap = true;
188 return this;
189 }
190
191 @Override
192 MapMaker maximumSize(int maximumSize) {
193 if (this.maximumSize != -1) {
194 throw new IllegalStateException("maximum size of " + maximumSize + " was already set");
195 }
196 if (maximumSize < 0) {
197 throw new IllegalArgumentException("invalid maximum size: " + maximumSize);
198 }
199 this.maximumSize = maximumSize;
200 useCustomMap = true;
201 return this;
202 }
203
204 @Override
205 public MapMaker concurrencyLevel(int concurrencyLevel) {
206 if (concurrencyLevel < 1) {
207 throw new IllegalArgumentException("GWT only supports a concurrency level of 1");
208 }
209
210
211 return this;
212 }
213
214 @Override
215 public <K, V> ConcurrentMap<K, V> makeMap() {
216 return useCustomMap
217 ? new ExpiringComputingMap<K, V>(expirationMillis, null, maximumSize, initialCapacity)
218 : new ConcurrentHashMap<K, V>(initialCapacity);
219 }
220
221 @Override
222 public <K, V> ConcurrentMap<K, V> makeComputingMap(Function<? super K, ? extends V> computer) {
223 return new ExpiringComputingMap<K, V>(
224 expirationMillis, computer, maximumSize, initialCapacity);
225 }
226 }